Skip to content

Conversation

m-bert
Copy link
Contributor

@m-bert m-bert commented Oct 1, 2025

Description

Right now, if one changes enabled property of handlers to false, they can never activate again. In the following example:

const [enabled, setEnabled] = useState(false);
const pan = Gesture.Pan().enabled(enabled)

gesture will never become active, even if enabled changes to true. Same happens when enabled is set to true and then changed to false and true again.

This happens because we set enabled to true by default in setGestureConfig, thus the following check:

if (config.enabled !== undefined && this.enabled !== config.enabled) { ... }

passes only when config.enabled is false.

To fix this, I've set enabled to null by default, and then if config.enabled is defined, it is assigned given value. Else default will be true.

Alternative approach would be to follow Android implementation and always react to changes in enabled even in cases like false $\rightarrow$ false and true $\rightarrow$ true.

Test plan

Tested on the following code:
import {
  GestureHandlerRootView,
  Gesture,
  NativeDetector,
  useGesture,
  SingleGestureName,
  GestureDetector,
} from 'react-native-gesture-handler';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import { useState } from 'react';
import { useSharedValue } from 'react-native-reanimated';

export default function App() {
  const [stateEnabled, setStateEnabled] = useState(false);
  const svEnabled = useSharedValue(false);

  const oldPan = Gesture.Pan()
    .enabled(stateEnabled)
    .onUpdate(() => {
      console.log('Old API');
    });

  const newPan = useGesture(SingleGestureName.Pan, {
    enabled: svEnabled,

    onUpdate: () => {
      'worklet';
      console.log('New API');
    },
  });

  return (
    <GestureHandlerRootView style={[styles.container, styles.center]}>
      <View style={[styles.boxContainer, styles.center]}>
        <Pressable
          onPress={() => setStateEnabled((prev) => !prev)}
          style={[styles.button, styles.center]}>
          <Text>Toggle old API</Text>
        </Pressable>
        <GestureDetector gesture={oldPan}>
          <View style={[styles.box, styles.center]} />
        </GestureDetector>
      </View>
      <View style={[styles.boxContainer, styles.center]}>
        <Pressable
          onPress={() => {
            svEnabled.value = !svEnabled.value;
          }}
          style={[styles.button, styles.center]}>
          <Text>Toggle new API</Text>
        </Pressable>
        <NativeDetector gesture={newPan}>
          <View style={[styles.box, styles.center]} />
        </NativeDetector>
      </View>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  center: {
    display: 'flex',
    justifyContent: 'space-around',
    alignItems: 'center',
  },
  container: {
    flex: 1,
  },
  boxContainer: {
    gap: 20,
  },
  button: {
    width: 150,
    height: 40,
    borderRadius: 20,
    backgroundColor: 'pink',
  },
  box: {
    width: 150,
    height: 150,
    backgroundColor: 'lightblue',
    borderRadius: 20,
  },
});

}

public setGestureConfig(config: Config) {
this.resetConfig();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the issue is that we reset the config here, but if the gesture was disabled before, we don't reattach the event listeners. Shouldn't we also try to attach listeners when resetting the config? At first glance, it looks like it could simplify this a lot (especially if it were to be done in a setter).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we also try to attach listeners when resetting the config?

But in that case if we have config that has enabled set to false we will first attach and then immediately detach listeners. Is that something that we want?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reverse that and start with detached listeners which are then attached when config has enabled !== false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of attaching listeners in resetConfig, we should detach them, and then attach if enabled !== false?

Copy link
Contributor Author

@m-bert m-bert Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the issue is that we reset the config here, but if the gesture was disabled before, we don't reattach the event listeners.

Also, I don't know whether we will have any other logic that depends on enabled change, but I think that we should say that what the issue really is is that in current setup it is impossible to detect that enabled has changed to true*. This may (or may not) cause other troubles in the future.

* If we go through resetConfig first, with SharedValue it should be fine.

@m-bert m-bert requested a review from j-piasecki October 3, 2025 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants